package config

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"
)

type Config struct {
	Name     string       `json:"name"`
	Listen   string       `json:"listen"`
	Log      LogConf      `json:"log"`
	Database DatabaseConf `json:"database"`
	Limits   LimitsConf   `json:"limits"`
	Reload   bool         `json:"reload"`
}

type LogConf struct {
	Level  string `json:"level"`
	Path   string `json:"path"`
	Max    int    `json:"max"`
	Rotate int    `json:"rotate"`
}

type DatabaseConf struct {
	Path    string `json:"path"`
	MaxOpen int    `json:"max_open"`
	MaxIdle int    `json:"max_idle"`
}

type LimitsConf struct {
	NameMaxLen        int `json:"name_maxlen"`
	DescriptionMaxLen int `json:"description_maxlen"`
	PolicyMaxNum      int `json:"policy_maxnum"`
	VsysMaxNum        int `json:"vsys_maxnum"`
}

var (
	C      Config
	once   sync.Once
	loaded bool
)

func Load(path string) error {
	var err error
	once.Do(func() {
		p := selectPath(path)
		raw, e := os.ReadFile(p)
		if e != nil {
			err = e
			return
		}
		if e = json.Unmarshal(raw, &C); e != nil {
			err = e
			return
		}
		applyDefaults(&C)
		if e = C.Validate(); e != nil {
			err = e
			return
		}
		loaded = true
	})
	if !loaded && err == nil {
		return errors.New("config not loaded")
	}
	return err
}

func MustLoad(path string) {
	if err := Load(path); err != nil {
		panic(err)
	}
}

func selectPath(p string) string {
	if p != "" {
		return p
	}
	if env := os.Getenv("RESTAPI_CONFIG"); env != "" {
		return env
	}
	return "etc/config.json"
}

func applyDefaults(c *Config) {
	if c.Name == "" {
		c.Name = "restapi"
	}
	if c.Listen == "" {
		c.Listen = "127.0.0.1:8080"
	}
	if c.Log.Level == "" {
		c.Log.Level = "info"
	}
	if c.Log.Path == "" {
		c.Log.Path = "restapi.log"
	}
	if c.Log.Max == 0 {
		c.Log.Max = 50
	}
	if c.Log.Rotate == 0 {
		c.Log.Rotate = 5
	}
	if c.Database.Path == "" {
		c.Database.Path = "database.db"
	}
	if c.Database.MaxOpen == 0 {
		c.Database.MaxOpen = 50
	}
	if c.Database.MaxIdle == 0 {
		c.Database.MaxIdle = 5
	}
	if c.Limits.NameMaxLen == 0 {
		c.Limits.NameMaxLen = 64
	}
	if c.Limits.DescriptionMaxLen == 0 {
		c.Limits.DescriptionMaxLen = 1024
	}
	if c.Limits.PolicyMaxNum == 0 {
		c.Limits.PolicyMaxNum = 1024
	}
	if c.Limits.VsysMaxNum == 0 {
		c.Limits.VsysMaxNum = 8
	}
}

func (c *Config) Validate() error {
	if c.Listen == "" {
		return errors.New("listen empty")
	}
	if c.Database.Path == "" {
		return errors.New("database.path empty")
	}
	if c.Limits.NameMaxLen <= 0 {
		return errors.New("name_maxlen <=0")
	}
	if c.Limits.PolicyMaxNum <= 0 {
		return errors.New("policy_maxnum <=0")
	}
	if c.Limits.VsysMaxNum <= 0 {
		return errors.New("vsys_maxnum <=0")
	}
	return nil
}

func EnsurePaths() error {
	if C.Log.Path != "" {
		if d := filepath.Dir(C.Log.Path); d != "." && d != "" {
			return os.MkdirAll(d, 0755)
		}
	}
	return nil
}

func ToJSON() string         { b, _ := json.MarshalIndent(C, "", "  "); return string(b) }
func Reader() io.Reader      { b, _ := json.Marshal(C); return bytes.NewReader(b) }
func Dump(path string) error { return os.WriteFile(path, []byte(ToJSON()), 0644) }
func LastLoadedInfo() string {
	return fmt.Sprintf("name=%s listen=%s db=%s log=%s", C.Name, C.Listen, C.Database.Path, C.Log.Path)
}
